//
//  YBCenter.m
//  YBNetworking
//
//  Created by ybgo on 2018/11/22.
//  Copyright © 2018 嘉联支付有限公司. All rights reserved.
//

#import "YBCenter.h"
#import "YBRequest.h"
#import "YBEngine.h"

@interface YBCenter ()
{
    dispatch_semaphore_t _lock;
}

@property (nonatomic, assign) NSUInteger autoIncrement;
@property (nonatomic, strong, readwrite) NSMutableDictionary<NSString *, id> *generalParameters;
@property (nonatomic, strong, readwrite) NSMutableDictionary<NSString *, NSString *> *generalHeaders;

@property (nonatomic, copy) YBCenterResponseProcessBlock responseProcessHandler;
@property (nonatomic, copy) YBCenterRequestProcessBlock requestProcessHandler;
@property (nonatomic, copy) YBCenterErrorProcessBlock errorProcessHandler;

@end

@implementation YBCenter

+ (instancetype)center {
    return [[[self class] alloc] init];
}

+ (instancetype)defaultCenter {
    
    static id sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [self center];
    });
    return sharedInstance;
}

- (instancetype)init {
    
    self = [super init];
    if (!self) {
        return nil;
    }
    _autoIncrement = 0;
    _lock = dispatch_semaphore_create(1);//最大开辟的线程数量，异步请求，每个请求要开辟新线程
    _engine = [YBEngine sharedEngine];
    return self;
}

#pragma mark - Public Instance Methods for YBCenter

- (void)setupConfig:(void(^)(YBConfig *config))block {
    
    YBConfig *config = [[YBConfig alloc] init];
    config.consoleLog = NO;
    YB_SAFE_BLOCK(block, config);
    
    if (config.generalServer) {
        self.generalServer = config.generalServer;
    }
    if (config.generalParameters.count > 0) {
        [self.generalParameters addEntriesFromDictionary:config.generalParameters];
    }
    if (config.generalHeaders.count > 0) {
        [self.generalHeaders addEntriesFromDictionary:config.generalHeaders];
    }
    if (config.callbackQueue != NULL) {
        self.callbackQueue = config.callbackQueue;
    }
    if (config.generalUserInfo) {
        self.generalUserInfo = config.generalUserInfo;
    }
    if (config.engine) {
        self.engine = config.engine;
    }
    self.consoleLog = config.consoleLog;
    
    if (config.operationMaxCount > 0) {
        self.operationMaxCount = config.operationMaxCount;
    }
}

- (void)setRequestProcessBlock:(YBCenterRequestProcessBlock)block {
    self.requestProcessHandler = block;
}

- (void)setResponseProcessBlock:(YBCenterResponseProcessBlock)block {
    self.responseProcessHandler = block;
}

- (void)setErrorProcessBlock:(YBCenterErrorProcessBlock)block {
    self.errorProcessHandler = block;
}

- (void)setGeneralHeaderValue:(NSString *)value forField:(NSString *)field {
    [self.generalHeaders setValue:value forKey:field];
}

- (void)setGeneralParameterValue:(id)value forKey:(NSString *)key {
    [self.generalParameters setValue:value forKey:key];
}

#pragma mark - 发送请求

- (NSString *)sendRequest:(YBRequestConfigBlock)configBlock {
    return [self sendRequest:configBlock onProgress:nil onSuccess:nil onFailure:nil onFinished:nil];
}

- (NSString *)sendRequest:(YBRequestConfigBlock)configBlock
                onSuccess:(nullable YBSuccessBlock)successBlock {
    return [self sendRequest:configBlock onProgress:nil onSuccess:successBlock onFailure:nil onFinished:nil];
}

- (NSString *)sendRequest:(YBRequestConfigBlock)configBlock
                onFailure:(nullable YBFailureBlock)failureBlock {
    return [self sendRequest:configBlock onProgress:nil onSuccess:nil onFailure:failureBlock onFinished:nil];
}

- (NSString *)sendRequest:(YBRequestConfigBlock)configBlock
               onFinished:(nullable YBFinishedBlock)finishedBlock {
    return [self sendRequest:configBlock onProgress:nil onSuccess:nil onFailure:nil onFinished:finishedBlock];
}

- (NSString *)sendRequest:(YBRequestConfigBlock)configBlock
                onSuccess:(nullable YBSuccessBlock)successBlock
                onFailure:(nullable YBFailureBlock)failureBlock {
    return [self sendRequest:configBlock onProgress:nil onSuccess:successBlock onFailure:failureBlock onFinished:nil];
}

- (NSString *)sendRequest:(YBRequestConfigBlock)configBlock
                onSuccess:(nullable YBSuccessBlock)successBlock
                onFailure:(nullable YBFailureBlock)failureBlock
               onFinished:(nullable YBFinishedBlock)finishedBlock {
    return [self sendRequest:configBlock onProgress:nil onSuccess:successBlock onFailure:failureBlock onFinished:finishedBlock];
}

- (NSString *)sendRequest:(YBRequestConfigBlock)configBlock
               onProgress:(nullable YBProgressBlock)progressBlock
                onSuccess:(nullable YBSuccessBlock)successBlock
                onFailure:(nullable YBFailureBlock)failureBlock {
    return [self sendRequest:configBlock onProgress:progressBlock onSuccess:successBlock onFailure:failureBlock onFinished:nil];
}

//公共请求
- (NSString *)sendRequest:(YBRequestConfigBlock)configBlock
               onProgress:(nullable YBProgressBlock)progressBlock
                onSuccess:(nullable YBSuccessBlock)successBlock
                onFailure:(nullable YBFailureBlock)failureBlock
               onFinished:(nullable YBFinishedBlock)finishedBlock {
    YBRequest *request = [YBRequest request];
    YB_SAFE_BLOCK(configBlock, request);
    
    [self yb_processRequest:request onProgress:progressBlock onSuccess:successBlock onFailure:failureBlock onFinished:finishedBlock];
    [self yb_sendRequest:request];
    
    return request.identifier;
}

#pragma mark -

- (void)cancelRequest:(NSString *)identifier {
    [self cancelRequest:identifier onCancel:nil];
}

- (void)cancelRequest:(NSString *)identifier
             onCancel:(nullable YBCancelBlock)cancelBlock {
    id request = nil;
    if (identifier.length > 0) {
        request = [self.engine cancelRequestByIdentifier:identifier];
    }
    YB_SAFE_BLOCK(cancelBlock, request);
}

- (id)getRequest:(NSString *)identifier {
    if (identifier == nil) {
        return nil;
    } else {
        return [self.engine getRequestByIdentifier:identifier];
    }
}

- (BOOL)isNetworkReachable {
    return self.engine.reachabilityStatus != 0;
}

- (YBNetworkConnectionType)networkConnectionType {
    return self.engine.reachabilityStatus;
}


#pragma mark - Public Class Methods for YBCenter

+ (void)setupConfig:(void(^)(YBConfig *config))block {
    [[YBCenter defaultCenter] setupConfig:block];
}

+ (void)setRequestProcessBlock:(YBCenterRequestProcessBlock)block {
    [[YBCenter defaultCenter] setRequestProcessBlock:block];
}

+ (void)setResponseProcessBlock:(YBCenterResponseProcessBlock)block {
    [[YBCenter defaultCenter] setResponseProcessBlock:block];
}

+ (void)setErrorProcessBlock:(YBCenterErrorProcessBlock)block {
    [[YBCenter defaultCenter] setErrorProcessBlock:block];
}

+ (void)setGeneralHeaderValue:(NSString *)value forField:(NSString *)field {
    [[YBCenter defaultCenter].generalHeaders setValue:value forKey:field];
}

+ (void)setGeneralParameterValue:(id)value forKey:(NSString *)key {
    [[YBCenter defaultCenter].generalParameters setValue:value forKey:key];
}

#pragma mark -

+ (NSString *)sendRequest:(YBRequestConfigBlock)configBlock {
    return [[YBCenter defaultCenter] sendRequest:configBlock onProgress:nil onSuccess:nil onFailure:nil onFinished:nil];
}

+ (NSString *)sendRequest:(YBRequestConfigBlock)configBlock
                onSuccess:(nullable YBSuccessBlock)successBlock {
    return [[YBCenter defaultCenter] sendRequest:configBlock onProgress:nil onSuccess:successBlock onFailure:nil onFinished:nil];
}

+ (NSString *)sendRequest:(YBRequestConfigBlock)configBlock
                onFailure:(nullable YBFailureBlock)failureBlock {
    return [[YBCenter defaultCenter] sendRequest:configBlock onProgress:nil onSuccess:nil onFailure:failureBlock onFinished:nil];
}

+ (NSString *)sendRequest:(YBRequestConfigBlock)configBlock
               onFinished:(nullable YBFinishedBlock)finishedBlock {
    return [[YBCenter defaultCenter] sendRequest:configBlock onProgress:nil onSuccess:nil onFailure:nil onFinished:finishedBlock];
}

+ (NSString *)sendRequest:(YBRequestConfigBlock)configBlock
                onSuccess:(nullable YBSuccessBlock)successBlock
                onFailure:(nullable YBFailureBlock)failureBlock {
    return [[YBCenter defaultCenter] sendRequest:configBlock onProgress:nil onSuccess:successBlock onFailure:failureBlock onFinished:nil];
}

+ (NSString *)sendRequest:(YBRequestConfigBlock)configBlock
                onSuccess:(nullable YBSuccessBlock)successBlock
                onFailure:(nullable YBFailureBlock)failureBlock
               onFinished:(nullable YBFinishedBlock)finishedBlock {
    return [[YBCenter defaultCenter] sendRequest:configBlock onProgress:nil onSuccess:successBlock onFailure:failureBlock onFinished:finishedBlock];
}

+ (NSString *)sendRequest:(YBRequestConfigBlock)configBlock
               onProgress:(nullable YBProgressBlock)progressBlock
                onSuccess:(nullable YBSuccessBlock)successBlock
                onFailure:(nullable YBFailureBlock)failureBlock {
    return [[YBCenter defaultCenter] sendRequest:configBlock onProgress:progressBlock onSuccess:successBlock onFailure:failureBlock onFinished:nil];
}

+ (NSString *)sendRequest:(YBRequestConfigBlock)configBlock
               onProgress:(nullable YBProgressBlock)progressBlock
                onSuccess:(nullable YBSuccessBlock)successBlock
                onFailure:(nullable YBFailureBlock)failureBlock
               onFinished:(nullable YBFinishedBlock)finishedBlock {
    return [[YBCenter defaultCenter] sendRequest:configBlock onProgress:progressBlock onSuccess:successBlock onFailure:failureBlock onFinished:finishedBlock];
}

#pragma mark -

+ (void)cancelRequest:(NSString *)identifier {
    [[YBCenter defaultCenter] cancelRequest:identifier onCancel:nil];
}

+ (void)cancelRequest:(NSString *)identifier
             onCancel:(nullable YBCancelBlock)cancelBlock {
    [[YBCenter defaultCenter] cancelRequest:identifier onCancel:cancelBlock];
}

+ (nullable id)getRequest:(NSString *)identifier {
    return [[YBCenter defaultCenter] getRequest:identifier];
}

+ (BOOL)isNetworkReachable {
    return [[YBCenter defaultCenter] isNetworkReachable];
}

+ (YBNetworkConnectionType)networkConnectionType {
    return [[YBCenter defaultCenter] networkConnectionType];
}


#pragma mark -

+ (void)addSSLPinningURL:(NSString *)url {
    [[YBCenter defaultCenter].engine addSSLPinningURL:url];
}

+ (void)addSSLPinningCert:(NSData *)cert {
    [[YBCenter defaultCenter].engine addSSLPinningCert:cert];
}

+ (void)addTwowayAuthenticationPKCS12:(NSData *)p12 keyPassword:(NSString *)password {
    [[YBCenter defaultCenter].engine addTwowayAuthenticationPKCS12:p12 keyPassword:password];
}

#pragma mark - Private Methods for YBCenter

- (void)yb_processRequest:(YBRequest *)request
               onProgress:(YBProgressBlock)progressBlock
                onSuccess:(YBSuccessBlock)successBlock
                onFailure:(YBFailureBlock)failureBlock
               onFinished:(YBFinishedBlock)finishedBlock {
    
    // set callback blocks for the request object.
    if (successBlock) {
        [request setValue:successBlock forKey:@"_successBlock"];
    }
    if (failureBlock) {
        [request setValue:failureBlock forKey:@"_failureBlock"];
    }
    if (finishedBlock) {
        [request setValue:finishedBlock forKey:@"_finishedBlock"];
    }
    if (progressBlock && request.requestType != kYBRequestNormal) {
        [request setValue:progressBlock forKey:@"_progressBlock"];
    }
    
    // add general user info to the request object.
    if (!request.userInfo && self.generalUserInfo) {
        request.userInfo = self.generalUserInfo;
    }
    
    // add general parameters to the request object.
    if (request.useGeneralParameters && self.generalParameters.count > 0) {
        NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
        [parameters addEntriesFromDictionary:self.generalParameters];
        if (request.parameters.count > 0) {
            [parameters addEntriesFromDictionary:request.parameters];
        }
        request.parameters = parameters;
    }
    
    // add general headers to the request object.
    if (request.useGeneralHeaders && self.generalHeaders.count > 0) {
        NSMutableDictionary *headers = [NSMutableDictionary dictionary];
        [headers addEntriesFromDictionary:self.generalHeaders];
        if (request.headers) {
            [headers addEntriesFromDictionary:request.headers];
        }
        request.headers = headers;
    }
    
    // process url for the request object.
    if (request.url.length == 0) {
        if (request.server.length == 0 && request.useGeneralServer && self.generalServer.length > 0) {
            request.server = self.generalServer;
        }
        if (request.api.length > 0) {
            NSURL *baseURL = [NSURL URLWithString:request.server];
            // ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected.
            if ([[baseURL path] length] > 0 && ![[baseURL absoluteString] hasSuffix:@"/"]) {
                baseURL = [baseURL URLByAppendingPathComponent:@""];
            }
            request.url = [[NSURL URLWithString:request.api relativeToURL:baseURL] absoluteString];
        } else {
            request.url = request.server;
        }
    }
    
    YB_SAFE_BLOCK(self.requestProcessHandler, nil, request);
    NSAssert(request.url.length > 0, @"The request url can't be null.");
}

- (void)yb_sendRequest:(YBRequest *)request {
    
    if (self.consoleLog) {
        if (request.requestType == kYBRequestDownload) {
            NSLog(@"\n============ [YBRequest Info] ============\nrequest download url: %@\nrequest save path: %@ \nrequest headers: \n%@ \nrequest parameters: \n%@ \n==========================================\n", request.url, request.downloadSavePath, request.headers, ((request.paramsType == YBRequestParamatersTypeDictinary) ? request.parameters: request.dataParamater));
        } else {
            NSLog(@"\n============ [YBRequest Info] ============\nrequest url: %@ \nrequest headers: \n%@ \nrequest parameters: \n%@ \n==========================================\n", request.url, request.headers, ((request.paramsType == YBRequestParamatersTypeDictinary)? request.parameters: request.dataParamater));
        }
    }
    // 设置最大请求数量
    if (self.operationMaxCount > 0) {
        [self.engine setConcurrentOperationCount:self.operationMaxCount];
    }
    // send the request through XMEngine.
    [self.engine sendRequest:request completionHandler:^(NSURLSessionDataTask * _Nullable task, id  _Nullable responseObject, NSError * _Nullable error) {
        // the completionHandler will be execured in a private concurrent dispatch queue.
        if (error) {
            [self yb_failureWithError:error task:task forRequest:request];
        } else {
            [self yb_successWithResponse:responseObject task:task forRequest:request];
        }
    }];
}

- (void)yb_successWithResponse:(id)responseObject task:(NSURLSessionDataTask *_Nullable)task forRequest:(YBRequest *)request {
    
    NSError *processError = nil;
    // custom processing the response data.
    id newResponseObject = YB_SAFE_BLOCK(self.responseProcessHandler, task, request, responseObject, &processError);
    if (newResponseObject) {
        responseObject = newResponseObject;
    }
    if (processError) {
        [self yb_failureWithError:processError task:task forRequest:request];
        return;
    }
    
    if (self.consoleLog) {
        if (request.requestType == kYBRequestDownload) {
            NSLog(@"\n============ [YBResponse Data] ===========\nrequest download url: %@\nresponse data: %@\n==========================================\n", request.url, responseObject);
        } else {
            if (request.responseSerializerType == YBResponseSerializerRAW) {
                NSLog(@"\n============ [YBResponse Data] ===========\nrequest url: %@ \nresponse data: \n%@\n==========================================\n", request.url, [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding]);
            } else {
                NSLog(@"\n============ [YBResponse Data] ===========\nrequest url: %@ \nresponse data: \n%@\n==========================================\n", request.url, responseObject);
            }
        }
    }
    
    if (self.callbackQueue) {
        __weak __typeof(self)weakSelf = self;
        dispatch_async(self.callbackQueue, ^{
            __strong __typeof(weakSelf)strongSelf = weakSelf;
            [strongSelf yb_execureSuccessBlockWithResponse:responseObject task:task forRequest:request];
        });
    } else {
        // execure success block on a private concurrent dispatch queue.
        [self yb_execureSuccessBlockWithResponse:responseObject task:task forRequest:request];
    }
}

- (void)yb_execureSuccessBlockWithResponse:(id)responseObject task:(NSURLSessionDataTask *)task forRequest:(YBRequest *)request {
    YB_SAFE_BLOCK(request.successBlock, task, responseObject);
    YB_SAFE_BLOCK(request.finishedBlock, task, responseObject, nil);
    [request cleanCallbackBlocks];
}

- (void)yb_failureWithError:(NSError *)error task:(NSURLSessionDataTask *_Nullable)task forRequest:(YBRequest *)request {
    
    if (self.consoleLog) {
        NSLog(@"\n=========== [YBResponse Error] ===========\nrequest url: %@ \nerror info: \n%@\n==========================================\n", request.url, error);
    }
    
    YB_SAFE_BLOCK(self.errorProcessHandler, task, request, &error);
    
    if (request.retryCount > 0) {
        request.retryCount --;
        // retry current request after 2 seconds.
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [self yb_sendRequest:request];
        });
        return;
    }
    
    if (self.callbackQueue) {
        __weak __typeof(self)weakSelf = self;
        dispatch_async(self.callbackQueue, ^{
            __strong __typeof(weakSelf)strongSelf = weakSelf;
            [strongSelf yb_execureFailureBlockWithError:error task:task forRequest:request];
        });
    } else {
        // execure failure block in a private concurrent dispatch queue.
        [self yb_execureFailureBlockWithError:error task:task  forRequest:request];
    }
}

- (void)yb_execureFailureBlockWithError:(NSError *)error task:(NSURLSessionDataTask *_Nullable)task forRequest:(YBRequest *)request {
    YB_SAFE_BLOCK(request.failureBlock, task, error);
    YB_SAFE_BLOCK(request.finishedBlock, task, nil, error);
    [request cleanCallbackBlocks];
}

#pragma mark - Accessor

- (NSMutableDictionary<NSString *, id> *)generalParameters {
    if (!_generalParameters) {
        _generalParameters = [NSMutableDictionary dictionary];
    }
    return _generalParameters;
}

- (NSMutableDictionary<NSString *, NSString *> *)generalHeaders {
    if (!_generalHeaders) {
        _generalHeaders = [NSMutableDictionary dictionary];
    }
    return _generalHeaders;
}

@end

#pragma mark - YBConfig

@implementation YBConfig
@end
